package java2d.game.sudoku;

import com.formdev.flatlaf.fonts.roboto_mono.FlatRobotoMonoFont;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

/**
 * Cell是最小显示单元，可以显示1个确定数字，或多个候选数字。
 */
public class SudokuCell extends JPanel
{
    private static final int SIZE = 72;
    private static final Font FONT_DIGIT = new Font(FlatRobotoMonoFont.FAMILY, Font.PLAIN, 60);
    private static final Font FONT_HINT = new Font(FlatRobotoMonoFont.FAMILY, Font.PLAIN, 18);
    private static final Constants.Colors COLORS = Constants.DARK;

    private final SudokuBoard board;
    private final int row, column;

    private boolean selected;
    private boolean hovered;
    private boolean highlightsInput;
    private boolean highlightsCandidate;
    private int highlightedNumber;

    public SudokuCell(SudokuBoard board, int row, int column)
    {
        this.board = board;
        this.row = row;
        this.column = column;

        Dimension dim = new Dimension(SIZE, SIZE);
        setPreferredSize(dim);
        setMaximumSize(dim);
        setMinimumSize(dim);

        setBackground(COLORS.board());
        setBorder(BorderFactory.createLineBorder(COLORS.board().darker(), 1));

        addMouseListener(new MouseHandler());

        selected = false;
        highlightsInput = false;
        highlightsCandidate = false;
        highlightedNumber = 0;
    }

    public int getRow()
    {
        return row;
    }

    public int getColumn()
    {
        return column;
    }

    public int getInput()
    {
        return board.getModel().getCell(row, column).getInput();
    }

    public void clearInput()
    {
        highlightsCandidate = false;
        highlightedNumber = 0;
        board.getModel().clearCellInput(row, column);
    }

    public void setInput(int input)
    {
        highlightsCandidate = false;
        highlightedNumber = 0;

        SudokuModel model = board.getModel();
        model.setCellInput(row, column, input);
    }

    public void clearCandidates()
    {
        highlightsCandidate = false;
        highlightedNumber = 0;

        SudokuModel model = board.getModel();
        model.clearCellCandidates(row, column);
    }

    public void toggleCandidate(int number)
    {
        highlightsCandidate = false;
        highlightedNumber = 0;

        SudokuModel model = board.getModel();
        model.clearCellInput(row, column);
        model.toggleCellCandidate(row, column, number);
    }

    public void setSelected(boolean selected)
    {
        boolean oldSelection = this.selected;
        this.selected = selected;
        if (oldSelection != selected)
            repaint();
    }

    public void setHovered(boolean hovered)
    {
        boolean oldHover = this.hovered;
        this.hovered = hovered;
        if (oldHover != hovered)
            repaint();
    }

    public void highlightsInput()
    {
        highlightsInput = true;
        repaint();
    }

    public void highlightCandidate(int number)
    {
        highlightsCandidate = true;
        highlightedNumber = number;
        repaint();
    }

    public void clearMarks()
    {
        selected = false;
        highlightsInput = false;
        highlightsCandidate = false;
        highlightedNumber = 0;
        repaint();
    }

    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g.create();

        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        Color paint = COLORS.board();
        if (hovered)
            paint = COLORS.hoverCell();
        if (selected)
            paint = COLORS.selectedCell();
        g2d.setPaint(paint);
        g2d.fillRect(0, 0, SIZE, SIZE);

        SudokuModel model = board.getModel();
        SudokuModel.Cell cell = model.getCell(row, column);
        if (cell.getInput() > 0)
        {
            if (highlightsInput)
            {
                g2d.setPaint(COLORS.highlightCell());
                g2d.fillRect(0, 0, SIZE, SIZE);
            }

            FontMetrics metrics = g.getFontMetrics(FONT_DIGIT);
            String digit = String.valueOf(cell.getInput());
            int width = metrics.stringWidth(digit);

            // 居中对齐
            int x = (SIZE - width) / 2;
            int baseline = SIZE / 2 + metrics.getAscent() / 3;

            paint = COLORS.input();
            if (cell.isAnchor())
                paint = COLORS.anchor();
            if (cell.isDuplicate())
                paint = COLORS.badInput();

            g2d.setFont(FONT_DIGIT);
            g2d.setPaint(paint);
            g2d.drawString(digit, x, baseline);
        } else
        {
            if (board.showCandidateNumbers())
            {
                int cellGridUnitW = SIZE / model.getGridColumnScale();
                int cellGridUnitH = SIZE / model.getGridRowScale();

                FontMetrics metrics = g.getFontMetrics(FONT_HINT);
                g2d.setFont(FONT_HINT);
                g2d.setPaint(COLORS.input());

                boolean[] candidateMarks = cell.getCandidateMarks();
                for (int r = 0; r < model.getGridRowScale(); r++)
                    for (int c = 0; c < model.getGridColumnScale(); c++)
                    {
                        int ni = r * model.getGridColumnScale() + c;
                        String digit = candidateMarks[ni] ? String.valueOf(ni + 1) : "";
                        int width = metrics.stringWidth(digit);

                        // 居中对齐
                        int x = c * cellGridUnitW + (cellGridUnitW - width) / 2 + 2;
                        int baseline = r * cellGridUnitH + cellGridUnitH / 2 + metrics.getAscent() / 3 + 2;

                        if (highlightsCandidate && ni + 1 == highlightedNumber)
                            g2d.setPaint(COLORS.anchor());
                        else
                            g2d.setPaint(COLORS.input());

                        g2d.drawString(digit, x, baseline);
                    }
            }
        }

        g2d.dispose();
    }

    private class MouseHandler extends MouseAdapter
    {
        @Override
        public void mouseClicked(MouseEvent e)
        {
            board.selectCell(SudokuCell.this);
        }

        @Override
        public void mouseEntered(MouseEvent e)
        {
            setHovered(true);
        }

        @Override
        public void mouseExited(MouseEvent e)
        {
            setHovered(false);
        }
    }
}
